<!--
 * @Author       : Hejh(3010733382@qq.com)
 * @Version      : V1.0
 * @Date         : 2024-02-15 14:27:49
 * @Description  : 用户选择器
-->

<template>
  <n-tree-select
    :value="value"
    class="my-tree-select"
    size="small"
    block-line
    :options="options"
    key-field="key"
    label-field="label"
    placeholder="选择用户"
    :show-path="true"
    :multiple="multiple"
    filterable
    :render-label="renderLabel"
    :render-tag="renderMultipleSelectTag"
    :override-default-node-click-behavior="override"
    :on-load="handleLoad"
    @update:value="handleValueChange"
    :to="false"
  />
</template>

<script setup lang="ts">
import type { VNodeChild } from 'vue'
import type {
  TreeSelectOption,
  TreeSelectOverrideNodeClickBehavior,
  SelectRenderTag
} from 'naive-ui'
import { getDepartmentByPage, getUserDepartmentByPage } from '@/api/department'
import { getUsersNotDep, getUsers } from '@/api/user'
import { COMMON_SEPARATOR } from '@/constants/index'
import { renderTag, renderUserLabel } from '@/utils/commonBusiness'
import { flattenArrayByChildren } from '@/utils'
import type { IndexObj } from '@/types/commom'

const emits = defineEmits<{
  (e: 'changeValue', value: string[] | string, options?: any[] | any): void
}>()

const props = withDefaults(
  defineProps<{
    defaultUserIds?: string[]
    defaultUserId?: string
    multiple?: boolean
    rootDepId?: string
    hasNotDepUser?: boolean
    canSelectDep?: boolean // 是否可以进行部门的选择
  }>(),
  {
    multiple: true,
    rootDepId: '1',
    hasNotDepUser: true,
    canSelectDep: false
  }
)

// 内部维护的选中项数组
const value = ref<string | string[]>([])

const override: TreeSelectOverrideNodeClickBehavior = ({ option }) => {
  if (option.isDep && !props.canSelectDep) {
    return 'toggleExpand'
  }
  return 'default'
}

const renderLabel = (info: {
  option: TreeSelectOption
  checked: boolean
  selected: boolean
}): VNodeChild => {
  return renderUserLabel(info.option)
}

// 渲染选择后的选项
const renderMultipleSelectTag: SelectRenderTag = ({ option, handleClose }) => {
  return renderTag(option, handleClose, () => renderUserLabel(option))
}

const options = ref<IndexObj[]>([])

onMounted(async () => {
  await loadRootDep()
  if (props.hasNotDepUser) {
    await loadUserNotDep()
  }
  initValue()
})

// 获取所有根部门
const loadRootDep = async () => {
  await getUserDepartmentByPage({
    page: 1,
    pageSize: 999,
    pid: props.rootDepId
  })
    .then((res) => {
      const newItems = res.data.items.map((item) => ({
        ...item,
        label: item.name,
        key: item.id,
        isDep: true,
        isLeaf: !!item.isLeaf && !!item.isUserLeaf
      }))

      options.value = options.value.concat(newItems)
    })
    .catch(console.error)

  // 还要获取根部门下的用户
  await getUsers({ page: 1, pageSize: 999, depId: props.rootDepId })
    .then((res) => {
      const newItems = res.data.items.map((item) => ({
        ...item,
        label: item.username,
        key: props.rootDepId + COMMON_SEPARATOR + item.id,
        isLeaf: true,
        isDep: false
      }))

      options.value = options.value.concat(newItems)
    })
    .catch(console.error)
}

// 获取所有不属于任何部门的用户
const loadUserNotDep = async () => {
  await getUsersNotDep({ page: 1, pageSize: 999 })
    .then((res) => {
      const newItems = res.data.items.map((item) => ({
        ...item,
        label: item.username,
        key: 'not-dep' + COMMON_SEPARATOR + item.id,
        isLeaf: true,
        isDep: false
      }))

      options.value = options.value.concat(newItems)
    })
    .catch(console.error)
}

// 非叶子节点进行加载
const handleLoad = (option: TreeSelectOption) => {
  return new Promise<void>((resolve) => {
    // 加载部门下的用户，加载部门下的子部门
    Promise.all([
      getDepartmentByPage({ page: 1, pageSize: 999, pid: option.id as string }),
      getUsers({ page: 1, pageSize: 999, depId: option.id as string })
    ])
      .then(([res1, res2]) => {
        const newDeps = res1.data.items.map((item) => {
          return {
            ...item,
            key: item.id,
            label: item.name,
            isLeaf: !!item.isLeaf && !!item.isUserLeaf,
            isDep: true
          }
        })

        const newUsers = res2.data.items.map((item) => {
          return {
            ...item,
            key: option.id + COMMON_SEPARATOR + item.id,
            label: item.username,
            isLeaf: true,
            isDep: false
          }
        })
        option.children = [...newDeps, ...newUsers]
        if (!option.children.length) {
          option.isLeaf = true
        }
      })
      .finally(() => {
        resolve()
      })
  })
}

// 监听options数组的变化，将它拍平后，选出其中ID于传入的defaultId相符的项目，赋值给value
const handleSelectOption = (newValue: IndexObj[]) => {
  // 将数组拍平
  const flatArr = flattenArrayByChildren(newValue)

  if (!props.multiple) {
    const res = flatArr.find((item) => item.id === props.defaultUserId)
    value.value = res ? res.key : ''
  } else {
    const res = flatArr
      .filter((item) => {
        return props.defaultUserIds?.some((v) => v === item.id)
      })
      .map((item) => item.key)
    value.value = res || []
  }
}

watch(
  () => options.value,
  (newValue) => {
    handleSelectOption(newValue)
  },
  {
    immediate: true,
    deep: true
  }
)

// TODO 可能需要当default信息发送变化的时候，要进行选中信息的更新

watch(
  props,
  (newValue) => {
    handleSelectOption(options.value)
  },
  {
    deep: true
  }
)

// 初始化value值
const initValue = () => {
  // if (props.defaultUserIds) {
  //   value.value = props.defaultUserIds
  // }
}

// 当select选中项发生了改变，我们可以拿到最新选中的数组，直接赋值给value，同时可以拿到对应的option,去重后传值给父组件
const handleValueChange = (
  value2: string[] | string,
  option: IndexObj[] | IndexObj
) => {
  // option中可能出现ID相同的，但key肯定不相同
  // 这里我们可以直接返回，也就是value2不做处理，而option是否去重交给父组件
  value.value = value2 // key值

  // 回传userId值，
  if (option instanceof Array) {
    // 需要将userId进行去重
    const ids = [
      ...new Set(option.map((item: IndexObj) => item.id))
    ] as string[]
    emits('changeValue', ids, option)
  } else {
    emits('changeValue', option ? option.id : '', option)
  }
}
</script>

<style scoped lang="scss"></style>
